package im.composer.media.sound.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Objects;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;

import org.tritonus.share.sampled.FloatInputStream;
import org.tritonus.share.sampled.FloatSampleBuffer;
import org.tritonus.share.sampled.FloatSampleInput;

public class LoopingFloatInputStream extends FloatInputStream {

	private long interval_frames = Long.MAX_VALUE;

	public LoopingFloatInputStream(AudioInputStream sourceStream) {
		super(sourceStream);
		if (getFrameLength() < 0) {
			throw new IllegalArgumentException("Unknown length!");
		}
		mark(Integer.MAX_VALUE);
	}

	public LoopingFloatInputStream(FloatSampleInput sourceInput, AudioFormat format, long frameLength) {
		super(sourceInput, format, frameLength);
		if (getFrameLength() < 0) {
			throw new IllegalArgumentException("Unknown length!");
		}
		mark(Integer.MAX_VALUE);
	}

	public LoopingFloatInputStream(InputStream sourceStream, AudioFormat format, long frameLength) {
		super(sourceStream, format, frameLength);
		if (getFrameLength() < 0) {
			throw new IllegalArgumentException("Unknown length!");
		}
		mark(Integer.MAX_VALUE);
	}

	public long getIntervalFrames() {
		return interval_frames;
	}

	public void setIntervalFrames(long interval_frames) {
		this.interval_frames = interval_frames;
	}

	public void setIntervalSeconds(double interval_seconds) {
		interval_frames = (long) (interval_seconds * getFormat().getSampleRate());
	}

	@Override
	public void reset() throws IOException {
		super.reset();
		try {
			Field field = FloatInputStream.class.getDeclaredField("eofReached");
			Field _field = FloatInputStream.class.getDeclaredField("sourceStream");
			field.setAccessible(true);
			_field.setAccessible(true);
			InputStream is = this;
			while (is != null && is instanceof FloatInputStream) {
				FloatInputStream fis = (FloatInputStream) is;
				field.set(fis, false);
				is = (InputStream) _field.get(fis);
			}
		} catch (Exception e) {
			throw new IOException(e);
		}
	}

	@Override
	public void read(FloatSampleBuffer buffer, int offset, int sampleCount) {
		Objects.requireNonNull(buffer);
		try {
			checkLoop();
		} catch (IOException e) {
		}
		FloatSampleBuffer fsb = new FloatSampleBuffer(getFormat().getChannels(), sampleCount, getFormat().getSampleRate());
		long pos = getPosition();
		super.read(fsb, offset, sampleCount);
		if(fsb.getSampleCount()<sampleCount){
			buffer.makeSilence();
		}
		fsb.copyTo(buffer, offset, fsb.getSampleCount());
		pos += sampleCount;
		setPosition(pos);
	}

	@Override
	public int read(byte[] abData, int nOffset, int nLength) throws IOException {
		checkLoop();
		long pos = getPosition();
		int i = super.read(abData, nOffset, nLength);
		if (i < 0) {
			for (int j = nOffset; j < nOffset + nLength; j++) {
				abData[j] = 0;
			}
			i = nLength;
		}
		pos += nLength / getFormat().getFrameSize();
		setPosition(pos);
		return i;
	}

	private void setPosition(long position) {
		try {
			Field field = FloatInputStream.class.getDeclaredField("position");
			field.setAccessible(true);
			field.set(this, position);
		} catch (Exception e) {
		}
	}

	private void checkLoop() throws IOException {
		if (getPosition() > interval_frames) {
			reset();
		}
	}

}
